package com.dmatek.uwb.local.xml.middleware;
import io.netty.buffer.Unpooled;
import io.netty.channel.socket.DatagramPacket;

import java.util.List;
import java.util.Map;

import com.dmatek.uwb.local.algorithm.FilterHopsSingleLocalModel;
import com.dmatek.uwb.local.algorithm.IFilterHopsModel;
import com.dmatek.uwb.local.algorithm.ILocalAlgorithm;
import com.dmatek.uwb.local.algorithm.SingleLocalAlgorithm;
import com.dmatek.uwb.packet.bean.AnchorType;
import com.dmatek.uwb.packet.bean.AssistLocation;
import com.dmatek.uwb.packet.bean.ENodeInfor;
import com.dmatek.uwb.packet.bean.SocketAnchorInfor;
import com.dmatek.uwb.packet.bean.TagInfor;
import com.dmatek.uwb.packet.ianalysis.IMonitorLocatorPack;
import com.dmatek.uwb.packet.ianalysis.UwbMonitorBootStrap;
import com.dmatek.uwb.packet.ianalysis.callback.IReportCallBack;
import com.dmatek.uwb.packet.ianalysis.handler.AnchorReportHandler;
import com.dmatek.uwb.packet.ianalysis.handler.HeartRateReportHandler;
import com.dmatek.uwb.packet.ianalysis.handler.PlayVoiceResponseHandler;
import com.dmatek.uwb.packet.ianalysis.handler.TagReportHandler;
import com.dmatek.uwb.util.tool.AnalyticalUtil;
import com.dmatek.uwb.util.tool.NetWorkUtil;
import com.dmatek.uwb.xml.container.IXmlParse;
import com.dmatek.uwb.xml.container.XmlContainer;
import com.dmatek.uwb.xml.dom.bean.BaseNode;
import com.dmatek.uwb.xml.dom.bean.BasePath;
import com.dmatek.uwb.xml.dom.bean.Config;
import com.dmatek.uwb.xml.dom.bean.Divider;
import com.dmatek.uwb.xml.dom.bean.Region;
import com.dmatek.uwb.xml.util.iassign.CommonAssignTool;
import com.dmatek.uwb.xml.util.iassign.IAssignAttributeTool;
import com.dmatek.uwb.xml.util.iparse.bean.BindNodeListParse;
import com.dmatek.uwb.xml.util.iparse.bean.BindNodeParse;
import com.dmatek.uwb.xml.util.iparse.bean.ConfigParse;
import com.dmatek.uwb.xml.util.iparse.bean.DividerMapParse;
import com.dmatek.uwb.xml.util.iparse.bean.DividerParse;
import com.dmatek.uwb.xml.util.iparse.bean.NodeMapParse;
import com.dmatek.uwb.xml.util.iparse.bean.NodeParse;
import com.dmatek.uwb.xml.util.iparse.bean.PathMapParse;
import com.dmatek.uwb.xml.util.iparse.bean.PathParse;
import com.dmatek.uwb.xml.util.iparse.bean.PointParse;
import com.dmatek.uwb.xml.util.iparse.bean.RegionMapParse;
import com.dmatek.uwb.xml.util.iparse.bean.RegionParse;
/**
 * 解析配置的中间类
 * @author zhangfu
 * @data 2019年12月4日 下午1:40:34
 * @Description
 */
public class LocalConfigMiddle extends BaseLocalConfigMiddle {
	private IAssignAttributeTool iAssignAttributeTool;
	private IXmlParse iXmlParse;
	private IMonitorLocatorPack<BaseNode> iMonitorLocatorPack;
	private ILocalAlgorithm<TagInfor, AssistLocation> singleLocalAlgorithm;
	// 卡片上报的处理器
	private TagReportHandler tagPackHandler;
	// 基站上报的处理器
	private AnchorReportHandler anchorPackHandler;
	// 心率上报处理器
	private HeartRateReportHandler heartRateHandler;
	// 播放语音处理器
	private PlayVoiceResponseHandler playVoiceResponseHandler;
	// 基站类型对象：语音播放成功后调用回调函数回传的对象
	private volatile AnchorType playAnchorType = null;
	// 播放语音使用的锁
	private volatile Object playLock = new Object();
	// 固定的基站类型 0x1
	private final byte FIX_ANCHOR_TYPE = 0x1;
	// 默认等待6 s任然没有响应，则认为没有设置成功
	private final int TIMEOUT_MS = 6000;
	// 采用的编码类型
	private final String ENCODE_TYPE = "GBK";
	public LocalConfigMiddle(String xmlPath, IReportCallBack<TagInfor> tagReportCallBack, 
		IReportCallBack<ENodeInfor> eNodeReportCallBack) {
		super(xmlPath, tagReportCallBack, eNodeReportCallBack);
		this.iAssignAttributeTool = new CommonAssignTool();
		this.iXmlParse = new XmlContainer(new ConfigParse(iAssignAttributeTool), 
				new BindNodeListParse(new BindNodeParse(iAssignAttributeTool)),
				new PointParse(iAssignAttributeTool), 
				new NodeMapParse(new NodeParse(iAssignAttributeTool)), 
				new PathMapParse(new PathParse(iAssignAttributeTool)),
				new RegionMapParse(new RegionParse(iAssignAttributeTool)),
				new DividerMapParse(new DividerParse(iAssignAttributeTool)));
	}
	/***
	 * 开始监听
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void start(String ip, int port) throws Exception {
		Map<String, Object> xmlContainer = iXmlParse.parse(getXmlPath());
		Config config = (Config) xmlContainer.get("config");
		Map<String, BaseNode> nodesMap = (Map<String, BaseNode>) xmlContainer
				.get("nodes");
		Map<String, BasePath> pathsMap = (Map<String, BasePath>) xmlContainer
				.get("paths");
		Map<String, Region> regionsMap = (Map<String, Region>) xmlContainer
				.get("regions");
		Map<String, Divider> dividersMap = (Map<String, Divider>) xmlContainer
				.get("dividers");
		IFilterHopsModel iFilterHopsModel = new FilterHopsSingleLocalModel(
				config, pathsMap);
		singleLocalAlgorithm = new SingleLocalAlgorithm(config, pathsMap,
				nodesMap, regionsMap, dividersMap, iFilterHopsModel);
		// 创建数据包处理器: 卡片数据包处理器、基站数据包处理器、心率数据包处理器、语音播报响应对象处理器
		tagPackHandler = new TagReportHandler(getTagReportCallBack(),
				singleLocalAlgorithm, nodesMap);
		anchorPackHandler = new AnchorReportHandler(geteNodeReportCallBack());
		heartRateHandler = new HeartRateReportHandler(tagPackHandler);
		playVoiceResponseHandler = new PlayVoiceResponseHandler(
				new PlayVoiceResponseCallBack());
		// 生成监听启动类
		iMonitorLocatorPack = new UwbMonitorBootStrap(tagPackHandler,
				anchorPackHandler, heartRateHandler, playVoiceResponseHandler);
 		String localIp = "";
 		/**
 		 * 当本机只有一个Ip地址时, 就采用当前的ip地址
 		 * 当本机有多个ip地址时优先选择配置文件中的ip地址
 		 * 配置文件中的ip地址无效时，则采用函数参数中携带的ip地址
 		 */
		List<String> ips = NetWorkUtil.getAllIps();
		if (ips.size() > 1) {
			String configIp = config.getIp();
			if (checkIp(configIp)) {
				localIp = configIp;
			} else if (checkIp(ip)) {
				localIp = ip;
			} else {
				throw new Exception("ip地址无效");
			}
		} else if (ips.size() == 1) { // 说明本地只存在一个ipv4的地址，只使用这一个地址
			localIp = ips.get(0);
		} else { // 本机没有ipv4的地址需要抛出异常
			throw new Exception("本机ipv4不存在");
		}
		int localPort, configPort;
		configPort = config.getPort();
		/**
		 * 优先使用配置文件中的端口，配置文件端口无效以后, 才使用函数参数中携带的ip地址
		 */
		if (checkPort(configPort)) {
			localPort = configPort;
		} else if (checkPort(port)) {
			localPort = port;
		} else {
			throw new Exception("端口无效");
		}
		iMonitorLocatorPack.init(localIp, localPort, nodesMap);
		iMonitorLocatorPack.start();
	}
	/***
	 * 结束接听
	 */
	@Override
	public void stop() throws Exception {
		if (null != iMonitorLocatorPack) {
			iMonitorLocatorPack.stop();
		}
	}
	/***
	 * 获取当前状态
	 * @throws Exception 
	 */
	@Override
	public boolean isRunning() throws Exception {
		if (null != iMonitorLocatorPack) {
			return iMonitorLocatorPack.isRunning();
		}
		return false;
	}
	/***
	 * 播放音频
	 */
	@Override
	public boolean playVoice(String anchorId, String content) throws Exception {
		if (!isRunning()) { // 说明当前没有开启网络监听, 我们不能进行语音播报
			throw new Exception("网络监听未开启");
		}
		Map<String, SocketAnchorInfor> anchorsMap = anchorPackHandler
				.getEnodesMap();
		SocketAnchorInfor anchor = anchorsMap.get(anchorId);
		if (null == anchor) {
			throw new Exception("基站还没有上报");
		}
		// 此时构建数据包, 并向Anchor发送数据
		byte[] bytes = AnalyticalUtil.buildPlayVoiceBytes(anchorId, content,
				FIX_ANCHOR_TYPE, ENCODE_TYPE);
		DatagramPacket sendDatagramPacket = new DatagramPacket(
				Unpooled.copiedBuffer(bytes), anchor.getInetSocketAddress());
		// 向基站发送数据
		synchronized (playLock) {
			// 发送数据包之前必须将回传的对象置为null
			playAnchorType = null;
			// 向Anchor发送数据包
			iMonitorLocatorPack.sendData(sendDatagramPacket);
			// 保证发送完以后马上就返回数据, 我们再去等待就会无效, 所以需要加上一个判断
			if (null == playAnchorType) {
				// 锁住当前对象, 并等待一个超时时间结束, 如果有返回值会立即唤醒.
				playLock.wait(TIMEOUT_MS);
			}
			// 判断当前是否已经返回
			if (null != playAnchorType
			&& playAnchorType.getAnchorId().equalsIgnoreCase(anchorId)
			&& FIX_ANCHOR_TYPE == playAnchorType.getType()) {
				// 说明这次返回的是当前基站的语音播报
				return true;
			}
		}
		return false;
	}
	// 播放语音的应答包的回调函数
	final class PlayVoiceResponseCallBack implements IReportCallBack<AnchorType>{
		@Override
		public void callBack(AnchorType anchorType) throws Exception {
			synchronized (playLock) {
				// 将当前对象赋值给外部类的成员对象
				playAnchorType = anchorType;
				// playLock唤醒
				playLock.notify();
			}
		}
	}
	public IXmlParse getiXmlParse() {
		return iXmlParse;
	}
	@Deprecated
	@Override
	public boolean getState() throws Exception {
		if (null != iMonitorLocatorPack) {
			return iMonitorLocatorPack.isRunning();
		}
		return false;
	}
}
